// 版权归Epic Games, Inc.所有。保留所有权利。

#pragma once

#include "Components/SceneComponent.h"
#include "Types/SlateEnums.h"

#include "IndicatorDescriptor.generated.h"

#define UE_API LYRAGAME_API

class SWidget;
class UIndicatorDescriptor;
class ULyraIndicatorManagerComponent;
class UUserWidget;
struct FFrame;
struct FSceneViewProjectionData;

// 指示器投影结构体，用于处理指示器在屏幕上的投影计算
struct FIndicatorProjection
{
	bool Project(const UIndicatorDescriptor& IndicatorDescriptor, const FSceneViewProjectionData& InProjectionData, const FVector2f& ScreenSize, FVector& ScreenPositionWithDepth);
};

UENUM(BlueprintType)
enum class EActorCanvasProjectionMode : uint8
{
	ComponentPoint, // 组件点投影
	ComponentBoundingBox, // 组件边界框投影
	ComponentScreenBoundingBox, // 组件屏幕边界框投影
	ActorBoundingBox, // 角色边界框投影
	ActorScreenBoundingBox // 角色屏幕边界框投影
};

/**
 * 描述和控制活动指示器的类。强烈建议你的小部件实现IActorIndicatorWidget接口，以便能够绑定到相关数据。
 */
UCLASS(MinimalAPI, BlueprintType)
class UIndicatorDescriptor : public UObject
{
	GENERATED_BODY()
	
public:
	UIndicatorDescriptor() { }

public:
	/**
	 * 获取数据对象
	 * @return 当前数据对象
	 */
	UFUNCTION(BlueprintCallable)
	UObject* GetDataObject() const { return DataObject; }
	/**
	 * 设置数据对象
	 * @param InDataObject 要设置的数据对象
	 */
	UFUNCTION(BlueprintCallable)
	void SetDataObject(UObject* InDataObject) { DataObject = InDataObject; }
	
	/**
	 * 获取场景组件
	 * @return 当前场景组件
	 */
	UFUNCTION(BlueprintCallable)
	USceneComponent* GetSceneComponent() const { return Component; }
	/**
	 * 设置场景组件
	 * @param InComponent 要设置的场景组件
	 */
	UFUNCTION(BlueprintCallable)
	void SetSceneComponent(USceneComponent* InComponent) { Component = InComponent; }

	/**
	 * 获取组件插槽名称
	 * @return 组件插槽名称
	 */
	UFUNCTION(BlueprintCallable)
	FName GetComponentSocketName() const { return ComponentSocketName; }
	/**
	 * 设置组件插槽名称
	 * @param SocketName 要设置的插槽名称
	 */
	UFUNCTION(BlueprintCallable)
	void SetComponentSocketName(FName SocketName) { ComponentSocketName = SocketName; }

	/**
	 * 获取指示器小部件类
	 * @return 指示器小部件类
	 */
	UFUNCTION(BlueprintCallable)
	TSoftClassPtr<UUserWidget> GetIndicatorClass() const { return IndicatorWidgetClass; }
	/**
	 * 设置指示器小部件类
	 * @param InIndicatorWidgetClass 要设置的指示器小部件类
	 */
	UFUNCTION(BlueprintCallable)
	void SetIndicatorClass(TSoftClassPtr<UUserWidget> InIndicatorWidgetClass)
	{
		IndicatorWidgetClass = InIndicatorWidgetClass;
	}

public:
	// TODO 更好地组织这个
	TWeakObjectPtr<UUserWidget> IndicatorWidget; // 指示器小部件弱引用

public:
	/**
	 * 设置当指示器组件为空时是否自动移除
	 * @param CanAutomaticallyRemove 是否可以自动移除
	 */
	UFUNCTION(BlueprintCallable)
	void SetAutoRemoveWhenIndicatorComponentIsNull(bool CanAutomaticallyRemove)
	{
		bAutoRemoveWhenIndicatorComponentIsNull = CanAutomaticallyRemove;
	}
	/**
	 * 获取当指示器组件为空时是否自动移除
	 * @return 是否可以自动移除
	 */
	UFUNCTION(BlueprintCallable)
	bool GetAutoRemoveWhenIndicatorComponentIsNull() const { return bAutoRemoveWhenIndicatorComponentIsNull; }

	/**
	 * 是否可以自动移除
	 * @return 是否可以自动移除
	 */
	bool CanAutomaticallyRemove() const
	{
		return bAutoRemoveWhenIndicatorComponentIsNull && !IsValid(GetSceneComponent());
	}

public:
	// 布局属性
	//=======================

	/**
	 * 获取是否可见
	 * @return 是否可见
	 */
	UFUNCTION(BlueprintCallable)
	bool GetIsVisible() const { return IsValid(GetSceneComponent()) && bVisible; }
	
	/**
	 * 设置期望的可见性
	 * @param InVisible 是否可见
	 */
	UFUNCTION(BlueprintCallable)
	void SetDesiredVisibility(bool InVisible)
	{
		bVisible = InVisible;
	}

	/**
	 * 获取投影模式
	 * @return 当前投影模式
	 */
	UFUNCTION(BlueprintCallable)
	EActorCanvasProjectionMode GetProjectionMode() const { return ProjectionMode; }
	/**
	 * 设置投影模式
	 * @param InProjectionMode 要设置的投影模式
	 */
	UFUNCTION(BlueprintCallable)
	void SetProjectionMode(EActorCanvasProjectionMode InProjectionMode)
	{
		ProjectionMode = InProjectionMode;
	}

	// 水平对齐方式，用于在空间点上放置指示器
	/**
	 * 获取水平对齐方式
	 * @return 水平对齐方式
	 */
	UFUNCTION(BlueprintCallable)
	EHorizontalAlignment GetHAlign() const { return HAlignment; }
	/**
	 * 设置水平对齐方式
	 * @param InHAlignment 要设置的水平对齐方式
	 */
	UFUNCTION(BlueprintCallable)
	void SetHAlign(EHorizontalAlignment InHAlignment)
	{
		HAlignment = InHAlignment;
	}

	// 垂直对齐方式，用于在空间点上放置指示器
	/**
	 * 获取垂直对齐方式
	 * @return 垂直对齐方式
	 */
	UFUNCTION(BlueprintCallable)
	EVerticalAlignment GetVAlign() const { return VAlignment; }
	/**
	 * 设置垂直对齐方式
	 * @param InVAlignment 要设置的垂直对齐方式
	 */
	UFUNCTION(BlueprintCallable)
	void SetVAlign(EVerticalAlignment InVAlignment)
	{
		VAlignment = InVAlignment;
	}

	// 是否将指示器限制在屏幕边缘？
	/**
	 * 获取是否限制到屏幕
	 * @return 是否限制到屏幕
	 */
	UFUNCTION(BlueprintCallable)
	bool GetClampToScreen() const { return bClampToScreen; }
	/**
	 * 设置是否限制到屏幕
	 * @param bValue 是否限制到屏幕
	 */
	UFUNCTION(BlueprintCallable)
	void SetClampToScreen(bool bValue)
	{
		bClampToScreen = bValue;
	}

	// 如果限制到屏幕边缘，是否显示箭头？
	/**
	 * 获取是否显示限制到屏幕箭头
	 * @return 是否显示箭头
	 */
	UFUNCTION(BlueprintCallable)
	bool GetShowClampToScreenArrow() const { return bShowClampToScreenArrow; }
	/**
	 * 设置是否显示限制到屏幕箭头
	 * @param bValue 是否显示箭头
	 */
	UFUNCTION(BlueprintCallable)
	void SetShowClampToScreenArrow(bool bValue)
	{
		bShowClampToScreenArrow = bValue;
	}

	// 指示器在世界空间中的位置偏移
	/**
	 * 获取世界位置偏移
	 * @return 世界位置偏移
	 */
	UFUNCTION(BlueprintCallable)
	FVector GetWorldPositionOffset() const { return WorldPositionOffset; }
	/**
	 * 设置世界位置偏移
	 * @param Offset 世界位置偏移
	 */
	UFUNCTION(BlueprintCallable)
	void SetWorldPositionOffset(FVector Offset)
	{
		WorldPositionOffset = Offset;
	}

	// 指示器在屏幕空间中的位置偏移
	/**
	 * 获取屏幕空间偏移
	 * @return 屏幕空间偏移
	 */
	UFUNCTION(BlueprintCallable)
	FVector2D GetScreenSpaceOffset() const { return ScreenSpaceOffset; }
	/**
	 * 设置屏幕空间偏移
	 * @param Offset 屏幕空间偏移
	 */
	UFUNCTION(BlueprintCallable)
	void SetScreenSpaceOffset(FVector2D Offset)
	{
		ScreenSpaceOffset = Offset;
	}

	/**
	 * 获取边界框锚点
	 * @return 边界框锚点
	 */
	UFUNCTION(BlueprintCallable)
	FVector GetBoundingBoxAnchor() const { return BoundingBoxAnchor; }
	/**
	 * 设置边界框锚点
	 * @param InBoundingBoxAnchor 边界框锚点
	 */
	UFUNCTION(BlueprintCallable)
	void SetBoundingBoxAnchor(FVector InBoundingBoxAnchor)
	{
		BoundingBoxAnchor = InBoundingBoxAnchor;
	}

public:
	// 排序属性
	//=======================

	// 允许对指示器进行排序（在按深度排序之后），允许某些指示器组始终显示在其他指示器前面
	/**
	 * 获取优先级
	 * @return 优先级
	 */
	UFUNCTION(BlueprintCallable)
	int32 GetPriority() const { return Priority; }
	/**
	 * 设置优先级
	 * @param InPriority 优先级
	 */
	UFUNCTION(BlueprintCallable)
	void SetPriority(int32 InPriority)
	{
		Priority = InPriority;
	}

public:
	/**
	 * 获取指示器管理器组件
	 * @return 指示器管理器组件
	 */
	ULyraIndicatorManagerComponent* GetIndicatorManagerComponent() { return ManagerPtr.Get(); }
	/**
	 * 设置指示器管理器组件
	 * @param InManager 指示器管理器组件
	 */
	UE_API void SetIndicatorManagerComponent(ULyraIndicatorManagerComponent* InManager);
	
	/**
	 * 注销指示器
	 */
	UFUNCTION(BlueprintCallable)
	UE_API void UnregisterIndicator();

private:
	UPROPERTY()
	bool bVisible = true; // 是否可见
	UPROPERTY()
	bool bClampToScreen = false; // 是否限制到屏幕
	UPROPERTY()
	bool bShowClampToScreenArrow = false; // 是否显示限制到屏幕箭头
	UPROPERTY()
	bool bOverrideScreenPosition = false; // 是否覆盖屏幕位置
	UPROPERTY()
	bool bAutoRemoveWhenIndicatorComponentIsNull = false; // 当指示器组件为空时是否自动移除

	UPROPERTY()
	EActorCanvasProjectionMode ProjectionMode = EActorCanvasProjectionMode::ComponentPoint; // 投影模式
	UPROPERTY()
	TEnumAsByte<EHorizontalAlignment> HAlignment = HAlign_Center; // 水平对齐方式
	UPROPERTY()
	TEnumAsByte<EVerticalAlignment> VAlignment = VAlign_Center; // 垂直对齐方式

	UPROPERTY()
	int32 Priority = 0; // 优先级

	UPROPERTY()
	FVector BoundingBoxAnchor = FVector(0.5, 0.5, 0.5); // 边界框锚点
	UPROPERTY()
	FVector2D ScreenSpaceOffset = FVector2D(0, 0); // 屏幕空间偏移
	UPROPERTY()
	FVector WorldPositionOffset = FVector(0, 0, 0); // 世界位置偏移

private:
	friend class SActorCanvas; // 友元类

	UPROPERTY()
	TObjectPtr<UObject> DataObject; // 数据对象
	
	UPROPERTY()
	TObjectPtr<USceneComponent> Component; // 场景组件

	UPROPERTY()
	FName ComponentSocketName = NAME_None; // 组件插槽名称

	UPROPERTY()
	TSoftClassPtr<UUserWidget> IndicatorWidgetClass; // 指示器小部件类

	UPROPERTY()
	TWeakObjectPtr<ULyraIndicatorManagerComponent> ManagerPtr; // 指示器管理器组件弱引用

	TWeakPtr<SWidget> Content; // 内容弱引用
	TWeakPtr<SWidget> CanvasHost; // 画布宿主弱引用
};

#undef UE_API